{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c37a5436-ede4-4155-8bc5-371a2db2dc95",
   "metadata": {},
   "source": [
    "# Multi-port calibration"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d129477-219e-467b-98d4-a6dac16e798f",
   "metadata": {},
   "source": [
    "This example demonstrates how `MultiportSOLT` calibration class can be used to calibrate multi-port S-parameter measurement with three or more ports."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c758e1ad-2964-4a09-84be-4617360a1fad",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd5871dc-c4f4-4770-8380-5eca5ce8df37",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "import skrf\n",
    "from skrf.media import Coaxial\n",
    "\n",
    "skrf.stylely()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81667dc5-a04f-45b1-9303-e3a225d8060d",
   "metadata": {},
   "source": [
    "## Creating synthetic data\n",
    "\n",
    "First we create a synthetic error network."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6eaaf9bd-4371-4c34-91d4-8c7a38e4d205",
   "metadata": {},
   "outputs": [],
   "source": [
    "freq = skrf.F(1,100,100, unit='GHz')\n",
    "nports = 3\n",
    "# 1.0 mm coaxial media for calibration error boxes\n",
    "coax = Coaxial(freq, z0_port=50, Dint=0.44e-3, Dout=1.0e-3, sigma=1e8)\n",
    "\n",
    "# Create random error network\n",
    "# First 'nports' ports will connect to an ideal VNA\n",
    "# and the last 'nports' ports will connect to the DUT.\n",
    "Z = coax.random(n_ports = 2*nports, name = 'Z')\n",
    "\n",
    "# Zero leakage terms in the error network.\n",
    "port_type = lambda n: 'VNA' if n < nports else 'DUT'  # noqa: E731\n",
    "port_number = lambda n: n if n < nports else n - nports  # noqa: E731\n",
    "for i in range(2*nports):\n",
    "    for j in range(i+1, 2*nports):\n",
    "        # No connection between different VNA/DUT ports.\n",
    "        # No connection between VNA and DUT ports with different number.\n",
    "        if port_type(i) == port_type(j) or port_number(i) != port_number(j):\n",
    "            Z.s[:,i,j] = 0\n",
    "            Z.s[:,j,i] = 0\n",
    "\n",
    "# VNA switch terms. This is the termination impedance of each port\n",
    "# when the source is not connected to that port.\n",
    "gammas = []\n",
    "for i in range(nports):\n",
    "    gammas.append(coax.random(n_ports=1, name=f'gamma_{i}'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "404efab2-d9e7-486e-9a15-8d8ebe78dd7a",
   "metadata": {},
   "source": [
    "Function for measuring a network through the error network. Simply connects error network to DUT network and adds the termination impedances."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6256f193-ac9e-437a-b2b0-892abd50a820",
   "metadata": {},
   "outputs": [],
   "source": [
    "def measure(ntwk, Z, gammas, nports):\n",
    "    out = skrf.terminate_nport(skrf.connect(Z, nports, ntwk, 0, num=nports), gammas)\n",
    "    out.name = ntwk.name\n",
    "    return out"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e43b76c4-07d2-4778-b166-0669121030ed",
   "metadata": {},
   "source": [
    "Next, create calibration standards. They should be multi-port networks. With real VNA measurement it might be necessary to manually combine two-port standard measurements to N-ports.\n",
    "\n",
    "The required standard are open, short and match on each port and thru measurement from the first port to all the other ports."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50791a98-45f8-4645-b267-0ac92ba77c5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "o = coax.open(nports=nports, name='open')\n",
    "s = coax.short(nports=nports, name='short')\n",
    "m = coax.match(nports=nports, name='load')\n",
    "\n",
    "thru = coax.thru(name='thru')\n",
    "\n",
    "ideals = []\n",
    "# nports-1 thrus from port 0 to all other ports.\n",
    "for i in range(1, nports):\n",
    "    thru_i = skrf.twoport_to_nport(thru, 0, i, nports)\n",
    "    ideals.append(thru_i)\n",
    "\n",
    "ideals.extend([o,s,m])\n",
    "measured = [measure(k, Z, gammas, nports) for k in ideals]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d461dda7-6bac-412d-9ec3-ec72dbc0a3fe",
   "metadata": {},
   "source": [
    "Device to test the calibration. A simple three-way junction. Because error network is randomly generated and have very high reflections, the plotted S-parameters of the uncalibrated network are unrecognizable."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6d8da86-7c50-4e62-a388-3ce4ca859d94",
   "metadata": {},
   "outputs": [],
   "source": [
    "dut = coax.tee(name='dut')\n",
    "dut_meas = measure(dut, Z, gammas, nports)\n",
    "dut_meas.plot_s_db()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ae888d31-b231-4ee7-843a-cbcf22105f8d",
   "metadata": {},
   "source": [
    "## Calibration"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a348ead-25f6-496c-ab94-0e7d37261e6a",
   "metadata": {},
   "source": [
    "Create `MultiportSOLT` calibration class. The first parameter is a two-port calibration method that the class uses for calibrating the multi-port. In this case we use simple SOLT calibration. Other possibilities are for example `UnknownThru` and `LRRM`. Note that different calibration algorithms might require standards to be given in specific order and might require additional inputs such as `switch_terms`. Refer to the two-port calibration documentation for the required inputs."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8a7b0c7a-a6fe-4081-bde3-6a2237ac3e4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "cal = skrf.MultiportSOLT(method=skrf.SOLT, measured=measured, ideals=ideals)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95547c57-8974-4fee-b3c8-f4d2606137dc",
   "metadata": {},
   "source": [
    "Calibrate the DUT and plot the calibrated S-parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c37d730d-ac70-4224-95e8-aa36d472bd8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "dut_cal = cal.apply_cal(dut_meas)\n",
    "dut_cal.plot_s_db()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "764c809a-c212-4ce7-8e67-8e2cd0472720",
   "metadata": {},
   "source": [
    "The difference between corrected and original dut S-parameters should be small."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7d46fb3d-bdae-41b1-b155-efaf800b3b65",
   "metadata": {},
   "outputs": [],
   "source": [
    "np.max(np.abs(dut_cal.s - dut.s))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f97e71e2-560b-480a-b012-c7f69cee9a95",
   "metadata": {},
   "source": [
    "## MultiportCal\n",
    "\n",
    "The above `MultiportSOLT` class can only be used for SOLT style two-port calibrations that require only one transmissive standard between the two ports. Multi-port TRL requires thru and one or more lines between the ports which doesn't fit the above class interface. Also if for some reason different two-port calibration methods should be used for the port pairs the `MultiportSOLT` class is not able to do it. For those cases there is a lower level `MultiportCal`.\n",
    "\n",
    "This calibration requires dictionary of port pairs as input. 'method' key is the two-port calibration class and 'measured' are the measured networks. They can be either two-ports or N-ports. Other keys are passed to the two-port calibration routine. In this case we are using `UnknownThru` class which requires additional `switch_term` arguments. Note that two-port switch terms are in the reverse order from multi-port switch terms. Two-port switch terms are listed in the order of where the source is connected, but this is unspecified in case of multi-port switch terms and they are listed in the order of which port they terminate. It's also important to list the ideals and measured networks in the correct order required by the two-port calibration. `UnknownThru` assumes that thru should be listed last and order of the other standards doesn't matter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "31f54189-c4ba-485f-a904-896d588f62c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "ideals_01 = ideals[2:] + [ideals[0]]\n",
    "ideals_02 = ideals[2:] + [ideals[1]]\n",
    "meas_01 = measured[2:] + [measured[0]]\n",
    "meas_02 = measured[2:] + [measured[1]]\n",
    "\n",
    "cal_dict = {(0, 1): {'method': skrf.UnknownThru, 'ideals': ideals_01,\n",
    "                     'measured': meas_01, 'switch_terms':[gammas[1], gammas[0]]},\n",
    "            (0, 2): {'method': skrf.UnknownThru, 'ideals': ideals_02,\n",
    "                     'measured': meas_02, 'switch_terms':[gammas[2], gammas[0]]}}\n",
    "\n",
    "cal2 = skrf.MultiportCal(cal_dict)\n",
    "\n",
    "dut_cal2 = cal2.apply_cal(dut_meas)\n",
    "dut_cal2.plot_s_db()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0d47630",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
